//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections.Generic;
using WorkflowData;

namespace Microsoft.Research.ScientificWorkflow.Helper
{
    /// <summary>
    /// This class maps the data types from the DAP to types
    /// supported in Trident.
    /// </summary>
    class OpenDAPDataConverter
    {
        private List<object> ConvertArrayToList(object[] array)
        {
            List<object> objList = new List<object>();
            for (int i = 0; i < array.Length; ++i)
            {
                objList.Add(array[i]);
            }
            return objList;
        }

        /// <summary>
        /// Returns the corresponding clr type for the XDR type.
        /// </summary>
        /// <returns></returns>
        private Type GetClrType(string xdrType)
        {
            switch (xdrType)
            {
                case "Int32":
                    return typeof(Int32);
                case "Int16":
                    return typeof(Int16);
                case "Float32":
                    return typeof(float);
                case "Float64":
                    return typeof(double);
                default:
                    throw new ArgumentException("Unmapped Type");
            }
        }
        
        /// <summary>
        /// Converts the data from scalar to object.
        /// </summary>
        private object ConvertScalar(ref DataHolder input)
        {
            return input.GetData(0)[0];
        }

        /// <summary>
        /// Converts the data from Array to object.
        /// </summary>
        private object ConvertArray(ref DataHolder input)
        {
            return input.GetData(0);
        }

        /// <summary>
        /// This method converts the DataHolder object containing the
        /// data from the grid to the HyperCube.
        /// </summary>
        private object ConvertGrid(int index, ref OpenDAPURI query, ref DataHolder input)
        {
            // The HyperCube supports four dimensions. Thus the
            // array lengths for dataholder should not be more than
            // 5
            if (input.DataArrayCount > 5 || input.DataArrayCount <= 1)
                throw new ArgumentException("Incorrect Grid for conversion");

            InternalDAPType dapType = query.GetVariableInternalType(index);
            // Create the schema object first.
            HyperCubeSchema schema = new HyperCubeSchema(1); // Grid always has values for one variable.
            schema.SetName(0, dapType.MainName);
            schema.SetType(0, GetClrType(dapType.XDRTypes[0]));
            schema.SetDescription(0, string.Empty);

            if (input.DataArrayCount > 1)
            {
                schema.XDimensionName = dapType.VariableNames[1];
                schema.XDimensionType = GetClrType(dapType.XDRTypes[1]);
                schema.SetDimension(0, HyperCube.Axis.X);
            }
            if (input.DataArrayCount > 2)
            {
                schema.YDimensionName = dapType.VariableNames[2];
                schema.YDimensionType = GetClrType(dapType.XDRTypes[2]);
                schema.SetDimension(0, HyperCube.Axis.Y);
            }
            if (input.DataArrayCount > 3)
            {
                schema.ZDimensionName = dapType.VariableNames[3];
                schema.ZDimensionType = GetClrType(dapType.XDRTypes[3]);
                schema.SetDimension(0, HyperCube.Axis.Z);
            }
            if (input.DataArrayCount == 5)
            {
                schema.TDimensionName = dapType.VariableNames[4];
                schema.TDimensionType = GetClrType(dapType.XDRTypes[4]);
                schema.SetDimension(0, HyperCube.Axis.T);
            }

            // Create the HyperCube
            // Start by determining the axis lengths
            int xlen = (input.DataArrayCount > 1) ? input.GetData(1).Length : 1;
            int ylen = (input.DataArrayCount > 2) ? input.GetData(2).Length : 1;
            int zlen = (input.DataArrayCount > 3) ? input.GetData(3).Length : 1;
            int tlen = (input.DataArrayCount == 5) ? input.GetData(4).Length : 1;

            HyperCube cube = new HyperCube(schema, xlen, ylen, zlen, tlen);

            // Assign the cube dimension values
            cube.XDimensionValues = (input.DataArrayCount > 1) ? ConvertArrayToList(input.GetData(1)) : null;
            cube.YDimensionValues = (input.DataArrayCount > 2) ? ConvertArrayToList(input.GetData(2)) : null;
            cube.ZDimensionValues = (input.DataArrayCount > 3) ? ConvertArrayToList(input.GetData(3)) : null;
            cube.TDimensionValues = (input.DataArrayCount == 5) ? ConvertArrayToList(input.GetData(4)) : null;

            int counter = 0;
            object[] values = input.GetData(0);
            System.Diagnostics.Debug.Assert((xlen * ylen * zlen * tlen) == values.Length);

            for (int x = 0; x < xlen; ++x)
            {
                for (int y = 0; y < ylen; ++y)
                {
                    for (int z = 0; z < zlen; ++z)
                    {
                        for (int t = 0; t < tlen; ++t)
                        {
                            cube.SetData(values[counter], x, y, z, t, 0);
                        }
                    }
                }
            }
            return cube;
        }

        /// <summary>
        /// Method to convert the types to Trident supported types.
        /// </summary>
        public object[] Convert(OpenDAPURI query, ref DataHolder[] inputData)
        {
            System.Diagnostics.Debug.Assert(query.VariableCount == inputData.Length);
            object[] outArray = new object[query.VariableCount];

            for (int i = 0; i < query.VariableCount; ++i)
            {
                string varName; OpenDAPURI.DapVariableType type;
                query.GetVariableDetails(i, out varName, out type);

                switch (type)
                {
                    case OpenDAPURI.DapVariableType.Float32:
                    case OpenDAPURI.DapVariableType.Float64:
                    case OpenDAPURI.DapVariableType.Int16:
                    case OpenDAPURI.DapVariableType.Int32:
                        outArray[i] = ConvertScalar(ref inputData[i]);
                        break;
                    case OpenDAPURI.DapVariableType.Array:
                        outArray[i] = ConvertArray(ref inputData[i]);
                        break;
                    case OpenDAPURI.DapVariableType.Grid:
                        outArray[i] = ConvertGrid(i, ref query, ref inputData[i]);
                        break;
                }
            }
            return outArray;
        }
    }
}
